#include "PageCache.h"

PageCache PageCache::_sInstance;//类外定义
//获取一个num页的span
Span* PageCache::NewSpan(size_t pageNum)
{
    assert((pageNum > 0));
    if(pageNum > (NPAGES - 1))//大于128页找堆申请
    {
        void* ptr = SystemAlloc(pageNum);
        Span* span = _spanPagePool.New();
        span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;//将内存地址转为页号
        span->_num = pageNum;
        //建立页号和span之间的映射
        _idSpanMap[span->_pageId] = span;
        return span;
    }
    //先检查第pageNum个桶里有没有span
    if(!_spanLists[pageNum].Empty())
    {
        Span* numSpan =  _spanLists[pageNum].PopFront();
        //建立每页和span的映射，方便中心缓存回收小块内存到对应span
        for(PAGE_ID i = 0; i < numSpan->_num; i++)
        {
            _idSpanMap[numSpan->_pageId + i] = numSpan;
        }
        numSpan->_isUse = true;
        return numSpan;
        
    }
    //检查一下后面的桶里有没有span，如果有就进行切分
    for(size_t i = pageNum + 1; i < NPAGES; i++)
    {
        if(!_spanLists[i].Empty())
        {
            //将后面更多页的桶中切出num页，剩下的放到对应页数的桶里
            Span* nSpan = _spanLists[i].PopFront();
            Span* mSpan = _spanPagePool.New();//new一个span来装申请页数的span

            mSpan->_pageId = nSpan->_pageId;
            mSpan->_num = pageNum;

            nSpan->_pageId += pageNum;
            nSpan->_num -= pageNum;

            _spanLists[nSpan->_num].PushFront(nSpan);//将多余的放到对应页数的桶里
            //存储nSpan的首尾页与span的映射，方便合并span时的前后页查找
            _idSpanMap[nSpan->_pageId] = nSpan;
            _idSpanMap[nSpan->_pageId + nSpan->_num - 1] = nSpan;
            //建立每页和span的映射，方便中心缓存回收小块内存到对应span
            for(PAGE_ID i = 0; i < mSpan->_num; i++)
            {
                _idSpanMap[mSpan->_pageId + i] = mSpan;
            }
            mSpan->_isUse = true;
            return mSpan;
        }
    }
    //到这一步说明page cache没有大页的span了，需要向系统申请，直接要一个128页的堆内存
    Span* bigSpan = _spanPagePool.New();
    void* ptr = SystemAlloc(NPAGES - 1);
    bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;//将返回来的堆内存地址转换成起始页号
    bigSpan->_num = NPAGES - 1;
    _spanLists[bigSpan->_num].PushFront(bigSpan);//放到128页对应桶里
    return NewSpan(pageNum);
    //可以不用递归，直接在这切分，但使用递归是为了养成代码复用的好习惯，虽然效率上会差一点点，但这点差异对于计算机来说基本不算什么

}
//获取对象到span的映射
Span* PageCache::MapObjectToSpan(void* obj)
{
    PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT;
    std::unique_lock<std::mutex> lock(_pageMutex);//构造时加锁，析构时自动解锁
    auto ret = _idSpanMap.find(id);
    if(ret != _idSpanMap.end())
    {
        return ret->second;
    }
    else
    {
        assert(false);
        return nullptr;
    }
}

void PageCache::ReleaseSpanToPageCache(Span* span)
{
    //如果大于128页直接释放回堆
    if(span->_num > (NPAGES - 1))
    {
        void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
        SystemFree(ptr);
        _spanPagePool.Delete(span);
        return;
    }
    //对前后页尝试合并，缓解内存碎片问题
    //向前合并
    while(1)
    {
        PAGE_ID preId = span->_pageId - 1;
        auto ret = _idSpanMap.find(preId);
        //前面的页号没有（未申请），停止向前合并
        if(ret == _idSpanMap.end())
        {
            break;
        }
        //前面页号对应span正在使用，停止向前合并
        Span* preSpan = ret->second;
        if(preSpan->_isUse )
        {
            break;
        }
        //合并出超过128页的span无法管理，停止向前合并
        if((preSpan->_num + span->_num) > (NPAGES - 1))
        {
            break;
        }
        //向前合并
        span->_pageId = preSpan->_pageId;
        span->_num += preSpan->_num;
        //将preSpan从带头双向循环链表中移除；
        _spanLists[preSpan->_num].Erase(preSpan);
         _spanPagePool.Delete(preSpan);
    }
    //向后合并
    while(1)
    {
        PAGE_ID nextId = span->_pageId + span->_num;
        auto ret = _idSpanMap.find(nextId);
        if(ret == _idSpanMap.end())
        {
            break;
        }
        Span* nextSpan = ret->second;
        if(nextSpan->_isUse)
        {
            break;
        }
        if((nextSpan->_num + span->_num) > (NPAGES - 1) )
        {
            break;
        }
        span->_num += nextSpan->_num;
        _spanLists[nextSpan->_num].Erase(nextSpan);
        _spanPagePool.Delete(nextSpan);
    }
    //将合并后的span挂到对应的双链表中
    _spanLists[span->_num].PushFront(span);
    //建立span与首尾页映射
    _idSpanMap[span->_pageId] = span;
    _idSpanMap[span->_pageId + span->_num - 1] = span;
    //将span设置为未使用状态
    span->_isUse = false;
}